package be.destin.skos.search;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;

import be.destin.skos.core.Concept;
import be.destin.skos.core.ConceptScheme;
import be.destin.skos.core.Count;
import be.destin.skos.core.NoScheme;
import be.destin.skos.core.ReferringApplication;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SchemeUsage;
import be.destin.skos.core.SkosManager;

public class CountCacher {

	static final Logger logger = Logger.getLogger(CountCacher.class);
	static final ConcurrentHashMap<String,CountCacher> openedCaches = new ConcurrentHashMap<String,CountCacher>(); 

	ConcurrentHashMap<String,FileChannel> fcs = new ConcurrentHashMap<String,FileChannel>();
	SchemeUsage usage = null;
	String prefix = null;

	private CountCacher(SchemeUsage su) {
		this.usage = su;
		openedCaches.put(su.key(), this);
		NoScheme aScheme = su.getInScheme();
		if (aScheme != null) {
			prefix = directory(aScheme.getManagerInstance())+aScheme.getAbout()+File.separator+usage.getApplication()+SchemeInterface.separator+usage.getRole();
		}
	}

	private static synchronized CountCacher getInstance(SchemeUsage su) {
		CountCacher cc = openedCaches.get(su);
		if (cc == null) {
			cc = new CountCacher(su);
		}
		return cc;
	}

	public static void store (Concept aConcept, String application, Count aCount) {
		if (aCount == null) return;
		ConceptScheme aScheme = aConcept.getInScheme();
		if (aScheme == null) return;
		SchemeUsage su = aScheme.getUsage(application,aCount.getRole());

		if (su.getDiskMirrorAddressSize() != 0) {
			CountCacher cc = getInstance(su);
			synchronized (cc) {
				FileChannel aFile = cc.filePos(aConcept);
				if (aFile != null) {
					try {
						ByteBuffer bfi = ByteBuffer.allocate(Integer.SIZE);
						bfi.putInt(aCount.getCount());
						aFile.write(bfi);
					} catch (NumberFormatException nfe) {
						logger.error("Failing to store in cache " + aConcept.getScheme_About() + ": ", nfe);
					} catch (IOException x) {
						logger.error("Failing to store in cache " + aConcept.getScheme_About() + ": ", x);
					}
				}
			}
		}

	}
	public static ReferringApplication retrieveAll  (Concept aConcept) {
		ConceptScheme aScheme = aConcept.getInScheme();
		for (SchemeUsage su : aScheme.getUsage()) {
			if (su.getDiskMirrorAddressSize() != 0) {
				CountCacher cc = getInstance(su);
				synchronized (cc) {
					FileChannel aFile = cc.filePos(aConcept);
					if (aFile != null) {
						try {
							ByteBuffer bfi = ByteBuffer.allocate(Integer.SIZE);
							aFile.read(bfi);
							int freq = bfi.getInt();
							aConcept.getUsage().getReferringApplication(su.getApplication()).addCount(su.getRole(), freq, null, true);
						} catch (NumberFormatException nfe) {
							logger.error("Failing to read in cache " + aConcept.getScheme_About() + ": ", nfe);
						} catch (IOException x) {
							logger.error("Failing to read in cache " + aConcept.getScheme_About() + ": ", x);
						}
					}
				}
			}
		}
		return null;		
	}

	public static String directory(SkosManager skosManager) {
		String configDir = skosManager.getConfigDirectory();
		return configDir+File.separator+"usage"+File.separator;
	}

	private FileChannel filePos(Concept aConcept) {
		long pos = 0; 
		NoScheme aScheme = this.usage.getInScheme();
		if (aScheme == null) return null;
		String filename = directory(aScheme.getManagerInstance())+aScheme.getAbout()+File.separator+usage.getApplication()+SchemeInterface.separator+usage.getRole()+SchemeInterface.separator;
		if (usage.getDiskMirrorAddressSize() != 0) {
			try {
				if (usage.getDiskMirrorAddressSize() < 0) {
					filename += aConcept.getAbout().substring(0,usage.getDiskMirrorAddressSize());
					pos = Long.parseLong(aConcept.getAbout().substring(usage.getDiskMirrorAddressSize()));
				} else {
					if (aConcept.getAbout().length() >= usage.getDiskMirrorAddressSize()) {
						filename += aConcept.getAbout().substring(0, aConcept.getAbout().length()-usage.getDiskMirrorAddressSize());
						pos = Long.parseLong(aConcept.getAbout().substring(aConcept.getAbout().length()-usage.getDiskMirrorAddressSize()));
					} else {
						pos = Long.parseLong(aConcept.getAbout());
					}
				}
			} catch (NumberFormatException nfe) {
				logger.error("Error in position for "+aConcept.getScheme_About(), nfe);
			}
		}

		FileChannel fc = fcs.get(filename);
		if (fc == null) {
			Path filepath = Paths.get(filename+".array4");
			try (FileChannel fcc = (FileChannel.open(filepath,EnumSet.of(StandardOpenOption.READ,StandardOpenOption.WRITE)))) {
				fc = fcc;
				fcs.put(filename, fc);
			} catch (IOException x) {
				logger.error("I/O error when opening "+filename+" for "+aConcept.getScheme_About() , x);
			}
		}
		try {
			fc.position(pos);
		}  catch (IOException x) {
			logger.error("I/O error when positionning at "+pos+" for "+aConcept.getScheme_About() , x);
		}
		return fc;
	}
}
